package midas.mw.crt.MD;

import java.util.Enumeration;
import java.util.Hashtable;

import midas.mw.cfg.CfgLogging;
import midas.mw.cfg.log.Logger;
import midas.mw.crt.common.IPMessage;

public class NodeListMD {
	
	/**
	 * HashTable that match each nodeId, whose messages waiting for its presence, 
	 * with its priority table.
	 * This structure maintains all messages stored in the MessageDispatcher. 
	 */
	private Hashtable nodes;
	
	/**
	 * String used to provide locks when several processes are using the same node
	 * destination and the PriorityTable of this node has not been created
	 */
	private static String noQueue = "NoQueue"; 	
    
	/**
	 * Constructor - creates a new instance of this class.
	 * This constructor creates a new NodeListMD data structure. It initializes
	 * the hashtable to store messages.  
	 */
	public NodeListMD ()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD()");		
		
		nodes = new Hashtable();
	}
	
	/**
	 * Inserts a IPMessage in the HashTable.
	 * This method inserts a new IPMessage. 
	 * It verifies if already exists a Priority Table for the destination node 
	 * of the message. If it doesn't exists, a new Priority Table is created.
	 * After this verification this method just inserts the message in its 
	 * corresponding Priority Table.
	 *   
	 * @param ipMessage Message to be inserted in the NodeListMD
	 */
	public void insertIPMessage(IPMessage ipMessage)
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//MessageDispatcher.enqueueMessage y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.insertIPMessage(IPmessage)");
		
		String destinationNodeId = ipMessage.getDestinationNodeId();			
		
		if (!nodes.containsKey(destinationNodeId))
			nodes.put(ipMessage.getDestinationNodeId(),new PriorityTable());
			
		((PriorityTable) nodes.get(destinationNodeId)).insertMessage(ipMessage);			
	}
	
	
	/**
	 * Retrieves if a Priority Table is empty for a specified node.
	 * This function returns true when the priority table is empty
	 * 
	 * @param nodeId The MIDAS node identifier
	 * @return True if the table is empty, false otherwise
	 */
	public boolean isEmptyNodeQueue(String nodeId)
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.isEmptyNodeQueue(" + nodeId + ")");
		
		if (!nodes.containsKey(nodeId) || ((PriorityTable) nodes.get(nodeId)).isEmptyTable())
			return true;
		else
			return false;
	}
	
	/**	 
	 * Returns the first message to be sent to the specified node, taking into 
	 * account the priority and time to live of the message.
	 *  
	 * @param nodeId MIDAS node identifier
	 * @return IPMessage to be sent; null if there is no one
	 */
	public IPMessage seeMessageNode(String nodeId)
	{
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//MessageDispatcher.processNodeAvailability y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.seeMessageNode(" + nodeId + ")");
		
		return  ((PriorityTable) nodes.get(nodeId)).seeFirstToBeSent();
	}
	
	/**
	 * Prints out the NodeListMD's structure.
	 */
	public synchronized void printStruct()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.printStruct()");
		
		Enumeration keynodes = nodes.keys();
		PriorityTable tabla;
		while(keynodes.hasMoreElements())
		{
			String nodeName = (String)keynodes.nextElement();
			tabla = (PriorityTable)nodes.get(nodeName);
			//System.out.println("---   " + tabla.seeFirstTobeSent().getDestinationNodeId() + "   -------------------------------------------------------");
			CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD..printStruct() \r\n --------------------" + nodeName + "--------------------");
			if (tabla.isEmptyTable())
				CfgLogging.logEvent(CfgLogging.LOG_INFO, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, 
						"NodeListMD().printStruct(). Node without messages");				
			else 
				tabla.printTable();
			CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "----------------------------------------------------------");
		}
	}
	
	/**
	 * Delete a message to be sent
	 * 
	 * @param ipMessage IPMessage to be deleted
	 */
	public synchronized void deleteMessage(IPMessage ipMessage)
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.deleteMessage(IPMessage)");
		
		String destinationNodeId = ipMessage.getDestinationNodeId();
		
		PriorityTable pt = (PriorityTable)nodes.get(destinationNodeId);
		if (pt != null)
		{
			pt.deleteMessage(ipMessage);
			if (isEmptyNodeQueue(destinationNodeId))					
				nodes.remove(destinationNodeId);
		}
	}
	
	/**
	 * This function is used to get an object to provide locks when several
	 * processes are trying to send messages to the same node. 
	 * 
	 * @param nodeId MIDAS node identifier
	 * @return Object to be monitored. If the node identifier has not created 
	 * its own priority table, it will return a String, otherwise its PriorityTable
	 */
	
	public Object getNodeQueue(String nodeId)
	{		
		//En este caso no hace falta sincronizar porque el unico sitio desde el que se la llama es desde
		//MessageDispatcher.processNodeAvailability y esta sincronizado
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.getNodeQueue(" + nodeId + ")");
		
		PriorityTable pt = (PriorityTable)nodes.get(nodeId);
		if (pt != null)
			return pt;
		else			
			return noQueue;			
	}
	
	/**
	 * It shows if the PriorityTable associated to a node is busy
	 * @param nodeId MIDAS node identifier
	 * @return Status of the PriorityTable associated to the node. It
	 * 			returns true if some process is sending a message, false otherwise
	 */
	public boolean getStatus(String nodeId)
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.getStatus(" + nodeId + ")");
		
		return ((PriorityTable) nodes.get(nodeId)).getStatus();
				
	}
	
	/**
	 * It sets the status of a PriorityTable associated to a node
	 * @param nodeId MIDAS node identifier
	 * @param status Status to be set up. True if some process is
	 * 		going to send a message to that node, false if the process
	 * 		has finished
	 */
	public synchronized void setStatus(String nodeId, boolean status)
	{		
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.setStatus(" + nodeId + ", " + status + ")");
		
		if (nodes.get(nodeId) != null)
			((PriorityTable) nodes.get(nodeId)).setStatus(status);
				
	}
	
	/**
	 * Provides the nodes structure stored by the Message Dispatcher
	 * @return The nodes structure stored by the Message Dispatcher
	 */
	public Hashtable getNodes()
	{
		CfgLogging.logEvent(CfgLogging.LOG_MCALL, Logger.COMPONENT_CRT, CfgLogging.SEV_DEBUG, "NodeListMD.getNodes()");
		
		return nodes;
	}
}

